iT邦幫忙

2026 iThome 鐵人賽

DAY 4
0
ChatGPT & Codex

我的音樂不該被平台綁架:30 天用 ChatGPT × Codex 打造跨平台 Music Recap系列 第 4

Day 4|YouTube Music 沒有播放秒數,那 Recap 還能算什麼?

  • 分享至 

  • xImage
  •  

昨天,我終於把自己的 YouTube Music 紀錄匯入成功,取得 15,403 筆音樂活動。

但資料能讀進來,只是第一步。接下來要決定的是:這些資料能支持哪些結論?

一筆活動能不能算成完整播放一次?同一個 Video ID 出現很多次,能不能說那就是我最愛的歌?沒有播放秒數時,總聆聽時間應該顯示什麼?

今天的工作,就是把這些問題變成程式會遵守的統計規則,替後面的 Recap 固定每個數字的意思。

這套規則稱為「指標契約」,也就是 metric contract。每個指標除了輸出結果,也要交代計算依據、資料覆蓋率,以及資料不足時該如何呈現。

先分清楚「有活動」與「聽了多久」

在這份 Takeout 資料裡,我取得了活動時間、Video ID、標題與頻道等資訊,卻沒有取得每筆實際播放秒數。

所以,我可以計算某個來源 ID 出現幾次、哪些頻道的活動較多,以及活動發生在哪些日期或時段。這些結果的依據都是「活動次數」。

但一筆活動不代表一首歌完整播放完畢。只靠這份紀錄,也無法知道當時聽了十秒、一分鐘,還是整首歌。

因此,同一份 Recap 裡的指標可能使用不同資料基礎。今天先把它們分成三種。

第一種是 activity_count,以活動次數計算。例如某個 Video ID 在這批資料中出現幾次。

第二種是 observed_played_ms,只使用來源直接提供的觀測播放毫秒數。它必須有對應的逐筆資料,不能由歌曲長度推算。

第三種是 platform_provided,保存平台另外提供的彙總結果,例如官方 Recap 顯示的總聆聽分鐘數。

之後每個指標都必須說明自己使用哪一種依據,不能只顯示一個看似精確的數字。

沒有播放秒數,不能顯示成 0 分鐘

這裡有一個容易忽略的差別:未知和 0 並不相同。

假設程式把所有有播放秒數的紀錄加起來。當一筆都沒有時,加總結果仍可能得到 0。但這個 0 只能表示沒有任何數值參與加總,不能拿來告訴使用者「你聽了 0 分鐘」。

今天完成的新規則會區分這兩種情況。

來源沒有提供觀測時長時,指標標示為 unavailable,數值保留 null。來源明確記錄 0 秒時,才標示為 available,並保留數值 0

另外,如果只有部分事件提供觀測時長,就只能得到那部分資料的時長小計,並且一起顯示它涵蓋多少筆事件。

要顯示涵蓋全部輸入事件的觀測時長總和,必須有事件,而且每一筆都有觀測時長。只要其中還有未知值,完整總和就維持無法計算。

估算值也會另外區分。即使某個欄位已經有數字,只要它的性質是 estimated,就不能混進觀測時長。

這樣,未來畫面上的「無資料」、「只有部分資料」與「明確為 0」才不會混在一起。

YouTube Music 的總時間,保留給官方 Recap

YouTube Music 的時間處理方式也固定下來了:第一版只採用官方 Recap 提供的總聆聽時間。

Takeout 逐筆活動的播放時間繼續保留未知。官方總值則另外保存平台來源、統計期間與數值,不分攤到每一首歌,也不拿來補齊活動紀錄的秒數。

例如,一份全年官方總值,和只涵蓋部分月份的活動資料,可能各自有參考價值,但它們的範圍並不相同。系統需要把期間說清楚,才能避免讓讀者以為兩者完全對得上。

同樣地,取得一份官方總值,也不代表每筆活動突然都有播放秒數。逐筆資料的時長覆蓋率,仍然要依照逐筆資料本身計算。

這一輪尚未提供官方 Recap 的總分鐘數,因此這個指標仍會顯示無資料。程式現在具備的是保存這類資料及標明依據的規則,沒有憑空產生我的聆聽時間。

百分比要看得見分母

除了數值本身,今天也固定了資料覆蓋率的定義。

覆蓋率,也就是 coverage,回答的是:「這個指標需要的資料,在本次輸入事件裡有多少筆可用?」

今天,我重新用原始 Takeout 檔案執行匯入與指標程式,得到以下真實結果:

資料項目 可用筆數/音樂活動總數 覆蓋率
來源 Video ID 15,403/15,403 100%
可用原始標題 15,289/15,403 約 99.26%
補值後可用頻道 15,376/15,403 約 99.82%
逐筆觀測播放時長 0/15,403 0%

這張表描述的是匯入資料裡的欄位可用程度。即使 Video ID 覆蓋率是 100%,也不能據此說帳號過去所有的收聽都完整匯出了。

還有一個差別,會直接影響之後的排行:排行占比的分母,和資料覆蓋率的分母,可能不同。

這批資料共有 15,403 筆活動,其中 15,376 筆有可用頻道,另外 27 筆仍無法歸屬頻道。

因此,某個頻道在「可歸屬頻道的活動」中占多少,分母應該使用 15,376。至於頻道資料本身的覆蓋率,分母才是全部 15,403 筆。

前者回答「這個頻道占可用頻道活動的多少」,後者回答「全部活動裡有多少能用來做頻道統計」。

這兩種百分比不能交換分母。現在每個 coverage 都會保存分子、分母及計算結果,之後才能回頭核對。分母為 0 時,比例保留 null,避免產生沒有意義的百分比。

有來源 ID,也不等於已經認出歌曲與歌手

今天全量驗收得到 1,197 個不同 Video ID。它可以支持來源 ID 層級的統計,但還不能直接說成 1,197 首完成辨識的歌曲。

同樣地,頻道名稱也不直接等於歌手。未來要產生真正的跨平台歌曲與歌手排行,仍然需要後續的辨識工作。

今天的程式會保留這個界線。

缺少標題的活動,只要仍有有效來源 ID,就能參與來源 ID 計數;可用頻道則用於頻道統計。程式不會因為缺少名稱就刪掉原本存在的活動,也不會把頻道自動改稱為歌手。

來源 ID 也會連同平台一起保留。後續即使接入其他平台,也不能只因為名稱或 ID 字串相似,就直接合併成同一首歌。

這些規則決定了 Recap 上能使用什麼名稱。現階段的結果可以稱為「來源 ID 活動統計」或「頻道活動統計」,跨平台歌曲與歌手的辨識則依照後面的排程處理。

今天實際完成與驗證了什麼

這一輪,我讓 ChatGPT 協助整理指標定義,並把規則寫成可執行的程式。

現在可以輸出活動數、來源 ID 數量、可參與來源 ID/頻道/時間統計的活動數,以及各種時長指標的可用或不可用狀態。每個結果都帶有固定的計算依據與資料覆蓋資訊。

驗證分成合成案例與真實資料兩部分。

合成案例刻意把未知時長、明確 0 秒與估算值放在一起,檢查程式會不會混算。這些測試確認:未知不會變成 0,估算值不會進入觀測時長,官方總值也不會回填到逐筆活動。

真實資料方面,今天使用原始 watch-history.html,從匯入到新指標計算完整跑過一次。

這次實際從 26,900 張活動卡片中匯入 15,403 筆 YouTube Music 活動,另外 11,497 筆一般 YouTube 活動按原規則略過,沒有拒絕或待分類的紀錄。

新指標確認了 1,197 個來源 ID、15,376 筆可用頻道活動,以及 15,403 筆可用時間紀錄。

時間解析沿用這份匯出的明確設定,將 CST 解釋為 UTC+08:00。這是本次資料的解析假設,不能推廣成所有 CST 都代表相同時差。

最關鍵的結果是:全部 15,403 筆活動的逐筆播放時間仍為未知,觀測時長與完整觀測時長都正確回傳 unavailablenull。原本沒有的資料,沒有在統計過程中變成 0 秒或估算分鐘。

我另外使用另一套 HTML 解析方式,從原始檔重新核算活動數、來源 ID 數量、標題與頻道覆蓋率,以及首末事件時間,結果與新指標輸出一致。這次獨立檢查核對的是這些彙總與時間邊界。

接下來,讓這些規則變成第一份 Recap

Day 4 完成了指標契約,也用自己的真實資料驗證了它。

現在,後續的 Recap 已經有一套固定規則:活動依活動次數計算,時長依實際可取得的資料呈現,百分比保留分母,無法計算的結果明確標示。

這讓資料缺口成為結果的一部分。即使某些欄位還不知道,也能清楚說明已經知道什麼。

Day 5 會接著產生第一份自己的 YouTube Music Recap,把來源 ID、頻道與日期、月份、時段分布,整理成真正可以閱讀的結果。


上一篇
Day 3 | 拿 15,403 筆真實 YouTube Music 紀錄驗收,哪些資料真的能拿來做 Recap?
下一篇
Day 5|第一份自己的 YouTube Music Recap:把 15,403 筆活動變成排行
系列文
我的音樂不該被平台綁架:30 天用 ChatGPT × Codex 打造跨平台 Music Recap5
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言